/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.util.*;
import java.io.*;
import java.net.Socket;
import java.sql.*;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

/**
 * This class handles the migration of dynamic database tables to static b+
 * trees. The database server needs to be well-tuned for this code to run
 * effectively.
 * 
 * @author bduphoff
 * 
 */
public class AutoMigrate extends Thread {

    /***************************************************************************
     * Used to determine when to migrate data older than n days.
     */
    private int numDays = 30;

    private int numDeleteDays = -1;

    /***************************************************************************
     * The minimum number of dynamic tables to keep, even if the data is very
     * old. These tables perform better than b+ trees but require more overhead.
     * Consider the cost-benefit trade-off when altering this value.
     */
    private int minDynamic = 3;

    private int maxDynamic = 1000;

    public AutoMigrate(int numDays, int minDynamic, int maxDynamic) {
        super();
        this.numDays = numDays;
        this.minDynamic = minDynamic;
        this.maxDynamic = maxDynamic;
    }

    /**
     * Sets the number of days to delete data after. Setting this to a positive
     * number will make the system behave as a ring buffer.
     * 
     * @param days
     *            The number of days to keep data in the system before deleting.
     *            A zero or negative value will keep data indefinitely.
     */
    public void setNumDeleteDays(int days) {
        this.numDeleteDays = days;
    }

    /***************************************************************************
     * Thread runs forever, continuously checking to see if dynamic tables need
     * to be converted to b+ trees.
     */
    public void run() {
        try {
            while (true) {

                boolean migrated = false;

                ArrayList<SensorInfo> sensors = NetFSE.getSensorInfo();
                int[] types = NetFSE.getTypes();

                /***************************************************************
                 * For each type in the system, find sensors that are ready to
                 * migrate. When a sensor is found to be in need of migration,
                 * send a request to the DataServer to initiate the migration.
                 */
                for (int i = 0; i < types.length; i++) {
                    String typeName = NetFSE.getTypeName(types[i]);
                    NetFSERecord rec = (NetFSERecord) NetFSE.TYPE_CLASSES.get(new Integer(types[i]));

                    /***********************************************************
                     * Check each sensor for this type to see if a migrate is
                     * needed.
                     */
                    for (int j = 0; j < sensors.size(); j++) {
                        SensorInfo sensor = sensors.get(j);
                        if (sensor.getToolID() == rec.getTypeID()) {
                            try {
                                Class.forName("org.postgresql.Driver");
                                String url = "jdbc:postgresql://" + NetFSE.METADATA_HOST + "/"
                                        + NetFSE.METADATA_DATABASE;
                                Properties props = new Properties();
                                props.setProperty("user", NetFSE.METADATA_USER);
                                props.setProperty("password", NetFSE.METADATA_PASSWORD);
                                props.setProperty("client_encoding", "UTF8");
                                Connection c = DriverManager.getConnection(url, props);

                                Statement s = c.createStatement();

                                /**
                                 * To start, determine if we are in ring buffer
                                 * mode and delete any old data 1) Delete old
                                 * data from the Temp_Tables 2) Delete static
                                 * indexes with Max_TS before the cutoff
                                 */
                                if (this.numDeleteDays > 0) {

                                    /**
                                     * Determine the point at which we should
                                     * start deleting old records.
                                     */
                                    GregorianCalendar cal = new GregorianCalendar();
                                    cal.setTimeInMillis(System.currentTimeMillis());
                                    cal.add(Calendar.DAY_OF_MONTH, -1 * numDeleteDays);

                                    // Oldest data to keep
                                    Timestamp maxTS = new Timestamp(cal.getTime().getTime());

                                    // Open connection to the local database
                                    // containing the transient tables
                                    Connection localConnection = NetFSE.createLocalDatabaseConnection(typeName
                                            .toLowerCase());
                                    Statement localStatement = localConnection.createStatement();

                                    // For each table, delete records before
                                    // maxTS
                                    String sql = "select * from Temp_Tables where Sensor_ID=" + sensor.getSensorID()
                                            + " and Node_ID=" + NetFSE.NODE_ID + " and (max_ts is null or max_ts < '" + maxTS + "' or '" + maxTS + "' between min_ts and max_ts)";
                                    System.out.println(sql);
                                    ResultSet rs = s.executeQuery(sql);
                                    while (rs.next()) {
                                        String tableName = rs.getString("table_name");
                                        String deleteSQL = "delete from " + tableName + " where startts < '" + maxTS
                                                + "' ";
                                        System.out.println("AutoMigrate deleting old data: " + deleteSQL);
                                        //localStatement.execute(deleteSQL);
                                    }
                                    rs.close();

                                    // Should determine new min/max TS in the
                                    // table and update temp_tables info

                                    localStatement.close();
                                    localConnection.close();

                                    long maxTime = maxTS.getTime();

                                    // Next check if there are static indices
                                    // that can be safely removed
                                    sql = "select * from Static_Indices a, Data_Info b where a.Sensor_ID=b.Sensor_ID and a.Run=b.Run and a.Index_Date=b.Index_Date and a.Sensor_ID="
                                            + sensor.getSensorID() + " and a.max_time < " + maxTime;
                                    rs = s.executeQuery(sql);
                                    while (rs.next()) {
                                        String indexPath = rs.getString("db_path");
                                        String expected = sensor.getName() + "/index/" + rs.getInt("year") + "/"
                                                + rs.getInt("month") + "/" + rs.getInt("day") + "_" + rs.getInt("run")
                                                + "/";
                                        /**
                                         * Before removing, check that the
                                         * metadata is formatted as expected so
                                         * as not to doing something bad
                                         */
                                        if (indexPath.contains(expected) && !indexPath.contains("..")) {
                                            String cmd = "rm -Rf " + indexPath;
                                            System.out.println("AutoMigrate deleting old data: " + cmd);
                                            //NetFSEUtil.execAndWait(cmd);
                                        } else {
                                            System.out.println("Invalid path in metadata: " + indexPath);
                                        }
                                        
                                        String dataPath = rs.getString("path");
                                        /**
                                         * No path should ever contain a ..,
                                         * that would be bad
                                         */
                                        if (!dataPath.contains("..")) {
                                            String cmd = "rm " + dataPath;
                                            System.out.println("AutoMigrate deleting old data: " + cmd);
                                            //NetFSEUtil.execAndWait(cmd);
                                        } else {
                                            System.out.println("Invalid path in metadata: " + indexPath);
                                        }
                                    }
                                    rs.close();
                                }

                                /***********************************************
                                 * Create a calendar now - numDays to determine
                                 * at which point to migrate data.
                                 */
                                GregorianCalendar cal = new GregorianCalendar();
                                cal.setTimeInMillis(System.currentTimeMillis());
                                cal.add(Calendar.DAY_OF_MONTH, -1 * numDays);
                                Timestamp maxTS = new Timestamp(cal.getTime().getTime());

                                boolean needToMigrate = false;

                                /********
                                 * First check to see if the max number of
                                 * dynamic indexes is exceeded.
                                 */
                                String sql = "select count(*) from Temp_Tables where Sensor_ID=" + sensor.getSensorID()
                                        + " and Node_ID=" + NetFSE.NODE_ID + " and max_ts is not null  ";
                                ResultSet rs = s.executeQuery(sql);
                                if (rs.next() && rs.getLong(1) > maxDynamic) {
                                    needToMigrate = true;
                                } else {
                                    rs.close();

                                    // Next, check for old data that needs to be
                                    // migrated
                                    sql = "select * from Temp_Tables where Sensor_ID=" + sensor.getSensorID()
                                            + " and Node_ID=" + NetFSE.NODE_ID
                                            + " and max_ts is not null and max_ts < '" + maxTS
                                            + "' order by max_ts desc offset " + minDynamic;
                                    rs = s.executeQuery(sql);
                                    if (rs.next()) {
                                        needToMigrate = true;
                                    }
                                }
                                rs.close();

                                if (needToMigrate) {

                                    long ts = System.currentTimeMillis();

                                    System.out.println("Starting migrate for " + typeName + ", sensor "
                                            + sensor.getSensorID() + ".");
                                    rs.close();

                                    // Connect to the DataServer to start a
                                    // migrate
                                    SocketFactory sf = null;
                                    if (NetFSE.USE_SSL_DATA)
                                        sf = SSLSocketFactory.getDefault();
                                    else
                                        sf = SocketFactory.getDefault();
                                    Socket sock = sf.createSocket("localhost", NetFSE.DATA_SERVER_PORT);

                                    DataInputStream dis = new DataInputStream(sock.getInputStream());
                                    DataOutputStream dos = new DataOutputStream(sock.getOutputStream());

                                    // Set the flags for the migrate
                                    dos.writeInt(sensor.getSensorID());
                                    dos.writeInt(DataSocket.OP_MIGRATE);
                                    int val = dis.readInt();
                                    val = dis.readInt(); // wait for the server

                                    if (val == 0) {
                                        // No migration was needed
                                        try {
                                            dos.writeInt(1);
                                            dos.flush();
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                        dos.close();
                                        dis.close();
                                        sock.close();
                                    } else {
                                        // Migration to finished
                                        try {
                                            dos.writeInt(1);
                                            dos.flush();
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                        dos.close();
                                        dis.close();
                                        sock.close();

                                        System.out.println("Migrate time was " + (System.currentTimeMillis() - ts)
                                                / 1000 + " seconds");
                                        migrated = true;
                                    }

                                }
                                s.close();
                                c.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }

                }

                // If nothing was done, wait 2 minutes,
                // else keep going and restart the loop
                if (!migrated) {
                    // System.out.println("Sleeping...");
                    Thread.sleep(2 * 60 * 1000); // sleep for 2 minutes
                }
            }
        } catch (Exception E) {
            E.printStackTrace();
        }
        System.out.println("AutoMigrate thread exited on an error");
    }

}
